 //===============================================================================
// Microsoft patterns & practices
// Guidance Explorer Offline Client
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Web;
using System.Windows.Forms;
using GuidanceExplorer;
using GuidanceExplorer.Controls;
using GuidanceExplorer.Model;
using GuidanceExplorer.Services;
using GuidanceExplorer.Views;
using GuidanceExplorerAddIn.Services;
using Microsoft.Office.Interop.Word;

namespace GuidanceExplorerAddIn.Views
{
    [ComVisible(true)]
    public partial class HtmlEditControl : UserControl
    {
        #region Private members

        /// <summary>
        /// The main model service instance.
        /// </summary>
        private IAddinModelStorageService _modelService;

        /// <summary>
        /// The HTML rendering service for the guidance items.
        /// </summary>
        private IHtmlRenderService _htmlRenderService;

        /// <summary>
        /// The guidance item that is being edited.
        /// </summary>
        private GuidanceItemFileReference _guidanceItem;

        /// <summary>
        /// The new images added to the guidance item.
        /// </summary>
        private List<string> _newImages = new List<string>();

        private string _oldHtml;

        /// <summary>
        /// The current read protection.
        /// </summary>
        private ReadProtection _readProtection;

        /// <summary>
        /// Flag for copying the HTML from the text box.
        /// </summary>
        private bool _copied = false;

        #endregion

        /// <summary>
        /// The HTML rendering service for the guidance items.
        /// </summary>
        public IHtmlRenderService HtmlRenderService
        {
            set { _htmlRenderService = value; }
        }

        /// <summary>
        /// The main model service instance.
        /// </summary>
        public IAddinModelStorageService ModelService
        {
            set { _modelService = value; }
        }

        public GEController Controller { private get; set; }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public HtmlEditControl()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Initialize the control with new data and also set the behavior of other controls.
        /// </summary>
        /// <param name="guidanceItem"></param>
        /// <param name="readProtection"></param>
        public void Initialize(GuidanceItemFileReference guidanceItem, ReadProtection readProtection)
        {
            _guidanceItem = guidanceItem;
            ReadProtection = readProtection;
            webBrowser.ObjectForScripting = this;
            webBrowser.Navigating += new WebBrowserNavigatingEventHandler(webBrowser_Navigating);
            webBrowser.IsWebBrowserContextMenuEnabled = false;
            exampleBrowser.IsWebBrowserContextMenuEnabled = false;
            templateBrowser.IsWebBrowserContextMenuEnabled = false;
            toolStrip.Enabled = false;

            //Set HTML for the item.
            if (readProtection == ReadProtection.ReadOnly)
            {
                webBrowser.DocumentStream = _htmlRenderService.GetReadOnlyDocumentStream(guidanceItem);
            }
            else if (readProtection == ReadProtection.ReadWrite)
            {
                webBrowser.DocumentStream = _htmlRenderService.GetEditableDocumentStream(guidanceItem);
                                
            }
            else
            {
                //Unsupported read protection mode.
                throw new NotImplementedException();
            }
            templateBrowser.DocumentStream = _htmlRenderService.GetReadOnlyTemplateStream(guidanceItem);
            exampleBrowser.DocumentStream = _htmlRenderService.GetReadOnlyExampleStream(guidanceItem);
        }

        /// <summary>
        /// Whether the Html has changed or not
        /// </summary>
        public bool HasChanged
        {
            get { return string.Compare(_oldHtml, GetHtml()) != 0; }
        }

        /// <summary>
        /// Called by the web browser when a URI is navigated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void webBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
        {
            GuidanceItem guidanceItem = null;
            if (e.Url.Scheme == Uri.UriSchemeHttp)
            {
                e.Cancel = true;
                WebBrowser browser = (WebBrowser)sender;
                browser.Navigate(e.Url, true);
            }
            else if (e.Url.Scheme == "ruleid")
            {
                e.Cancel = true;
                string ruleId = e.Url.AbsolutePath;
                guidanceItem = _modelService.FindItemById(ruleId);
                if (guidanceItem != null)
                {
                    GuidanceItemFileReference reference = guidanceItem as GuidanceItemFileReference;
                    if (reference != null)
                    {
                        webBrowser.DocumentStream = _htmlRenderService.GetReadOnlyDocumentStream(reference);
                    }
                }
            }
            else if (e.Url.Scheme == "ruledisplay")
            {
                e.Cancel = true;
                string ruleId = e.Url.AbsolutePath;
                guidanceItem = _modelService.Guidance.Items.FindItemById(ruleId);
                GuidanceItemFileReference gifr = _modelService.Guidance.GetGuidanceItemFileReference(guidanceItem);
                Controller.GuidanceItemOpened(this, new GuidanceItemFileReferenceEventArgs(new GuidanceItemFileReference[] { gifr }, ReadProtection.ReadOnly));
            }
            else if (e.Url.Scheme == "file")
            {
                string extension = Path.GetExtension(e.Url.AbsolutePath);
                if (string.Compare(extension, ".bmp", true) == 0 || string.Compare(extension, ".gif", true) == 0 ||
                    string.Compare(extension, ".jpg", true) == 0 || string.Compare(extension, ".jpeg", true) == 0 ||
                    string.Compare(extension, ".png", true) == 0)
                {
                    e.Cancel = true;
                    //string url = e.Url.AbsolutePath.Replace("/", "\\");
                    string url = HttpUtility.UrlDecode(e.Url.AbsolutePath);
                    _newImages.Add(url);
                    HtmlElement imgTag = webBrowser.Document.CreateElement("img");
                    imgTag.SetAttribute("src", url);
                    webBrowser.Document.ActiveElement.AppendChild(imgTag);
                }
            }

        }

        /// <summary>
        /// Get/Set the read protection of this editor.
        /// </summary>
        public ReadProtection ReadProtection
        {
            set
            {
                _readProtection = value;

                if (_readProtection == ReadProtection.ReadOnly)
                {
                    this.tabControl.SelectedIndex = 0;
                    tabControl.TabPages.Remove(tabHTMLPage);
                    tabControl.TabPages.Remove(tabTemplatePage);
                    tabControl.TabPages.Remove(tabPage1);
                    toolStrip.Visible = false;
                }
                else if (tabControl.TabPages.Count == 1)
                {
                    tabControl.TabPages.Add(tabHTMLPage);
                    tabControl.TabPages.Add(tabTemplatePage);
                    tabControl.TabPages.Add(tabPage1);
                    toolStrip.Visible = true;

                    //Set HTML for the item.
                    webBrowser.DocumentStream = _htmlRenderService.GetEditableDocumentStream(_guidanceItem as GuidanceItemFileReference);
                }
            }
            get { return _readProtection; }
        }

        /// <summary>
        /// Return the HTML from the browser.
        /// </summary>
        /// <returns></returns>
        public string GetHtml()
        {
            string retHtml = "";
            if (webBrowser.Document.Body != null)
            {
                if (tabControl.SelectedIndex == 1)
                {
                    retHtml = textBox.Text;
                }
                else
                {
                    retHtml = webBrowser.Document.Body.InnerHtml;
                }

                retHtml = _htmlRenderService.CleanHTML(retHtml);

                //look for images that are on the local machine and add them to _newImages
                retHtml = FindLocalImages(retHtml);

                foreach (string img in _newImages)
                {
                    retHtml = retHtml.Replace(img, "|image|" + Path.GetFileName(img));
                }

                foreach (KeyValuePair<string, string> img in _guidanceItem.ImagesMapping)
                {
                    retHtml = retHtml.Replace(img.Value, "|image|" + img.Key);
                }
            }
            return retHtml;
        }
        /// <summary>
        /// look for images that are on the local machine and add them to _newImages
        /// </summary>
        /// <param name="retHtml"></param>
        private string FindLocalImages(string retHtml)
        {
            string openTag = "<img";
            string srcTag = "src=\"";
            string imgSrc;
            string modifiedImgSrc;
            int openTagIndex = retHtml.ToLower().IndexOf(openTag);
            while (openTagIndex != -1)
            {
                int srcTagStartIndex = retHtml.ToLower().IndexOf(srcTag, openTagIndex);
                int srcTagEndIndex = retHtml.IndexOf("\"", srcTagStartIndex + srcTag.Length);
                if (srcTagStartIndex >= srcTagEndIndex)
                {
                    continue;
                }

                imgSrc = retHtml.Substring(srcTagStartIndex + srcTag.Length, srcTagEndIndex - srcTagStartIndex - srcTag.Length);
                if (imgSrc.Length != 0)
                {
                    modifiedImgSrc = imgSrc;

                    modifiedImgSrc = modifiedImgSrc.Replace("file:///", "");
                    modifiedImgSrc = modifiedImgSrc.Replace("file:\\\\", "");
                    modifiedImgSrc = modifiedImgSrc.Replace("file://", "");
                    modifiedImgSrc = modifiedImgSrc.Replace("FILE:///", "");
                    modifiedImgSrc = modifiedImgSrc.Replace("FILE:\\\\", "");
                    modifiedImgSrc = modifiedImgSrc.Replace("FILE://", "");

                    retHtml = retHtml.Replace(imgSrc, modifiedImgSrc);

                    if (!_newImages.Contains(modifiedImgSrc))
                    {
                        try
                        {
                            //make sure path exists on local machine before adding it
                            if (File.Exists(modifiedImgSrc))
                            {
                                _newImages.Add(modifiedImgSrc);
                            }
                        }
                        catch
                        {
                            //do nothing, path was bad and shouldn't be added
                        }
                    }
                }
                openTagIndex = retHtml.ToLower().IndexOf(openTag, srcTagEndIndex);
            }
            return retHtml;
        }

        /// <summary>
        /// Called from the script to enable and disable the UI buttons.
        /// </summary>
        /// <param name="cut"></param>
        /// <param name="copy"></param>
        /// <param name="paste"></param>
        /// <param name="undo"></param>
        /// <param name="redo"></param>
        /// <param name="bold"></param>
        /// <param name="italic"></param>
        /// <param name="underline"></param>
        /// <param name="left"></param>
        /// <param name="center"></param>
        /// <param name="right"></param>
        /// <param name="ordered"></param>
        /// <param name="unordered"></param>
        /// <param name="insertImage"></param>
        /// <param name="createLink"></param>
        /// <param name="format"></param>
        public void HandleEvent(bool cut, bool copy, bool paste, bool undo, bool redo, bool bold, bool italic, bool underline, bool left, bool center, bool right, bool ordered, bool unordered, bool insertImage, bool createLink, string format)
        {
            toolStrip.Enabled = true;
            tsbBold.Checked = bold;
            tsbItalic.Checked = italic;
            tsbUnderline.Checked = underline;

            tsbCut.Enabled = cut;
            tsbCopy.Enabled = copy;
            tsbPaste.Enabled = paste;
            tsbUndo.Enabled = undo;

            tsbJustifyLeft.Checked = left;
            tsbJustifyRight.Checked = right;
            tsbJustifyCenter.Checked = center;

            tsbNumbered.Checked = ordered;
            tsbBulleted.Checked = unordered;

            tsbInsertImage.Enabled = insertImage;
        }

        private void tsbBold_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Bold", false, null);
        }

        private void tsbItalic_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Italic", false, null);
        }

        private void tsbUnderline_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Underline", false, null);
        }

        private void tsbIndent_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Indent", false, null);
        }

        private void tsbOutdent_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Outdent", false, null);
        }

        private void tsbJustifyLeft_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("JustifyLeft", false, null);
        }

        private void tsbJustifyCenter_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("JustifyCenter", false, null);
        }

        private void tsbJustifyRight_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("JustifyRight", false, null);
        }

        private void tsbNumbered_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("InsertOrderedList", false, null);
        }

        private void tsbBulleted_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("InsertUnorderedList", false, null);
        }

        private void tsbCut_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Cut", false, null);
        }

        private void tsbCopy_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Copy", false, null);
        }

        private void tsbPaste_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Paste", false, null);
        }

        private void tbsUndo_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Undo", true, null);
        }

        private void tbsRedo_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("Redo", true, null);
        }

        private void tsbAddPRE_Click(object sender, EventArgs e)
        {
            webBrowser.Document.InvokeScript("insertCode");
        }

        private void tsbH1_Click(object sender, EventArgs e)
        {
            webBrowser.Document.InvokeScript("setHeading", new object[] { "1" });
        }

        private void tsbH2_Click(object sender, EventArgs e)
        {
            webBrowser.Document.InvokeScript("setHeading", new object[] { "2" });
        }

        private void tsbH3_Click(object sender, EventArgs e)
        {
            webBrowser.Document.InvokeScript("setHeading", new object[] { "3" });
        }

        private void tsbNormal_Click(object sender, EventArgs e)
        {
            webBrowser.Document.InvokeScript("setNormal", null);
        }

        private void tsbInsertTable_Click(object sender, EventArgs e)
        {
            CreateTable dialog = new CreateTable();
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                webBrowser.Document.InvokeScript("insertTable", new object[] { dialog.Rows, dialog.Cols });
            }
        }

        private void tsbInsertImage_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.CheckFileExists = true;
            ofd.CheckPathExists = true;
            ofd.RestoreDirectory = true;
            ofd.Filter = "Image files |*.jpg;*.gif;*.png;*.bmp";
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                _newImages.Add(ofd.FileName);
                webBrowser.Document.ExecCommand("InsertImage", false, ofd.FileName);
            }
        }

        private void tsbCreateLink_Click(object sender, EventArgs e)
        {
            webBrowser.Document.ExecCommand("CreateLink", true, null);
        }

        private void tabControl_Click(object sender, EventArgs e)
        {
            if (tabControl.SelectedIndex == 0)
            {
                toolStrip.Enabled = true;
                if (_copied)
                {
                    bool cancel = false;

                    cancel = ValidateHtml(true);

                    if (cancel)
                    {
                        return;
                    }
                }
                webBrowser.Focus();
            }
            else if (tabControl.SelectedIndex == 1)
            {
                _copied = true;
                toolStrip.Enabled = false;
                if (textBox.Text != webBrowser.Document.Body.InnerHtml)
                {
                    textBox.Text = webBrowser.Document.Body.InnerHtml;
                }
            }
            else
            {
                toolStrip.Enabled = false;
            }
        }

        public bool ValidateHtml(bool changingTabs)
        {
            string oldVersion = webBrowser.Document.Body.InnerHtml;
            if (tabControl.SelectedIndex == 1)
            {
                if (textBox.Text.IndexOf("<script", StringComparison.InvariantCultureIgnoreCase) != -1)
                {
                    MessageBox.Show("A SCRIPT element was found in the HTML code, which is forbidden, please change the HTML to avoid such element.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    tabControl.SelectedIndex = 1;
                    return true;
                }
                if (textBox.Text.IndexOf("<object", StringComparison.InvariantCultureIgnoreCase) != -1)
                {
                    MessageBox.Show("A OBJECT element was found in the HTML code, which is forbidden, please change the HTML to avoid such element.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    tabControl.SelectedIndex = 1;
                    return true;
                }
            }
            HtmlElementCollection elements = webBrowser.Document.Body.GetElementsByTagName("script");
            if (elements != null && elements.Count != 0)
            {
                webBrowser.Document.Body.InnerHtml = oldVersion;
                MessageBox.Show("A SCRIPT element was found in the HTML code, which is forbidden, please change the HTML to avoid such element.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                tabControl.SelectedIndex = 1;
                return true;
            }

            elements = webBrowser.Document.Body.GetElementsByTagName("object");
            if (elements != null && elements.Count != 0)
            {
                webBrowser.Document.Body.InnerHtml = oldVersion;
                MessageBox.Show("An OBJECT element was found in the HTML code, which is forbidden, please change the HTML to avoid such element.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                tabControl.SelectedIndex = 1;
                return true;
            }
            if (changingTabs && tabControl.SelectedIndex == 0)
            {
                if (webBrowser.Document.Body.InnerHtml != textBox.Text)
                {
                    webBrowser.Document.Body.InnerHtml = textBox.Text;
                }
            }
            return false;
        }

        public List<string> NewImages
        {
            get { return _newImages; }
            set { _newImages = value; }
        }

        [Browsable(false)]
        public string CSSPath
        {
            get
            {
                if (_guidanceItem != null)
                {
                    return _guidanceItem.CSSFile;
                }
                return null;
            }
            set
            {
                if (value != null)
                {
                    if (File.Exists(value) && _guidanceItem != null)
                    {
                        _guidanceItem.CSSFile = value;
                    }
                }
            }
        }

        private void tsbInsertItemLink_Click(object sender, EventArgs e)
        {
            string href = (string)webBrowser.Document.InvokeScript("getHref", null);
            GuidanceItem[] items = null;

            //Show the dialog.
            PickGuidanceItem pickGuidanceItem = new PickGuidanceItem();
            pickGuidanceItem.ModelService = _modelService;

            Form dialogForm = new Form();
            dialogForm.Controls.Add(pickGuidanceItem);
            pickGuidanceItem.Dock = DockStyle.Fill;
            dialogForm.MinimizeBox = false;
            dialogForm.MaximizeBox = true;
            dialogForm.Size = new Size(668, 479);

            if (dialogForm.ShowDialog() == DialogResult.OK)
            {
                items = pickGuidanceItem.GuidanceItems;

                foreach (GuidanceItem item in items)
                {
                    webBrowser.Document.InvokeScript("insertLink", new object[] { string.Format("ruledisplay:{0}", item.Id), item.Title + "\n" });
                }
            }
        }

        private void SpellCheck_Click(object sender, EventArgs e)
        {
            try
            {
                this.Cursor = Cursors.WaitCursor;

                string guidanceText = webBrowser.Document.Body.InnerText;
                ApplicationClass word = new ApplicationClass();

                object missing = Missing.Value;
                object visible = true;
                object saveChanges = false;
                object lang = WdLanguageID.wdEnglishUS;
                object dicc = WdLanguageID.wdEnglishUS;// word.Languages.get_Item(ref lang).ActiveSpellingDictionary;

                _Document doc = word.Documents.Add(ref missing, ref missing, ref missing, ref visible);
                doc.Words.First.InsertBefore(guidanceText);

                doc.Content.LanguageID = WdLanguageID.wdEnglishUS;
                ProofreadingErrors we = doc.SpellingErrors;

                List<string> spellingErrors = new List<string>(we.Count);
                Dictionary<string, List<string>> suggestionsDictionary = new Dictionary<string, List<string>>(we.Count);
                if (we.Count > 0)
                {
                    foreach (Range error in we)
                    {
                        if (!spellingErrors.Contains(error.Text))
                        {
                            SpellingSuggestions suggestions = word.GetSpellingSuggestions(error.Text, ref missing, ref missing, ref dicc, ref missing, ref missing,
                            ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);

                            List<string> suggestion = new List<string>(suggestions.Count);
                            for (int i = 1; i <= suggestions.Count; i++)
                            {
                                suggestion.Add(suggestions[i].Name);
                            }
                            suggestionsDictionary.Add(error.Text, suggestion);
                            spellingErrors.Add(error.Text);
                        }
                    }
                }
                else
                {
                    MessageBox.Show("The spelling check is completed", "Spell Checker", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                this.Cursor = Cursors.Default;

                webBrowser.Document.InvokeScript("showError", new object[] { spellingErrors[0] });
                SpellingCheckerDialog dialog = new SpellingCheckerDialog(suggestionsDictionary, spellingErrors);
                dialog.NextSpellingError += new EventHandler<NextSpellingErrorEventArgs>(dialog_NextSpellingError);
                dialog.ReplaceError += new EventHandler<ReplaceErrorEventArgs>(dialog_ReplaceError);

                dialog.ShowDialog();

                word.Quit(ref saveChanges, ref missing, ref missing);
            }
            finally
            {
                Cursor = Cursors.Default;
            }
        }

        void dialog_ReplaceError(object sender, ReplaceErrorEventArgs e)
        {
            webBrowser.Document.InvokeScript("replaceText", new object[] { e.Error, e.Replace });
        }

        void dialog_NextSpellingError(object sender, NextSpellingErrorEventArgs e)
        {
            webBrowser.Document.InvokeScript("showError", new object[] { e.Error });
        }

        private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            if (_oldHtml == null)
            {
                _oldHtml = webBrowser.Document.Body.InnerHtml;
            }
        }
    }
}
